﻿// Shader-Based-Image-Processing (SBIP)
// http://code.google.com/p/sbip/
//
// Copyright © Frank Nagl, 2009-2010
// admin@franknagl.de
//
namespace SBIP
{
    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using DirectShowLib;
    using Microsoft.Xna.Framework.Graphics;
    using Filter;
    using Color = Microsoft.Xna.Framework.Graphics.Color;

    /// <summary>Declares the event handler for a lost device exception. 
    /// </summary>
    public delegate void OnDeviceLostEventHandler();

     /// <summary>
    /// The core of the <c>S</c>hader-<c>B</c>ased-<c>I</c>mage <c>P</c>rocessing 
    /// framework. Manages all imaging processes.
    /// </summary>
    public class Processor
    {
        GraphicsDevice graphics;
        Texture2D texture;
        Texture2D original;  
        BaseFilter filter;        
        /// <summary>Windows control using for online rendering process.</summary>
        Control renderControl;

        /// <summary>
        /// Declares the event handler for a lost device exception in the 
        /// render loop.   
        /// </summary>
        public OnDeviceLostEventHandler OnDeviceLostEvent;
        /// <summary>True, if media is a video.</summary>
        public bool IsVideo { get; protected set; }
        /// <summary>
        /// True, if processor already begun, otherwise false.
        /// </summary>
        public bool HasBegun { get; protected set; }

        /// <summary>The shader filter to apply to.</summary>
        public BaseFilter Filter
        {
            get { return filter; }
            set
            {
                filter = value;
                filter.Init(texture, graphics);                
            }
        }

        /// <summary>VideoPlayer object.</summary>
        public VideoPlayer VideoPlayer { get; set; }

        // Initialize XNA's Graphics Device
        private void InitGraphics()
        {
            PresentationParameters pp = new PresentationParameters();
            pp.IsFullScreen = false;
            pp.SwapEffect = SwapEffect.Discard;
            pp.BackBufferWidth = renderControl.Width;
            pp.BackBufferHeight = renderControl.Height;
            pp.EnableAutoDepthStencil = true;
            pp.AutoDepthStencilFormat = DepthFormat.Depth24Stencil8;
            pp.PresentOptions = PresentOptions.None;
            pp.RenderTargetUsage = RenderTargetUsage.PreserveContents;

            graphics = new GraphicsDevice(GraphicsAdapter.DefaultAdapter,
                DeviceType.Hardware, renderControl.Handle, pp);         
        }

        // Checks, if processor already started. If yes, throw exception.
        private void CheckBegin()
        {
            if (HasBegun)
                throw new Exception
                    ("Processor already started. Call Processor.End() first.");
            //else
            HasBegun = true;
        }

        /// <summary>Fires the OnDeviceLostEvent.</summary>
        private void FireDeviceLostEvent()
        {
            //fire the event now
            if (OnDeviceLostEvent != null) //is there a EventHandler?
            {
                OnDeviceLostEvent.Invoke(); //calls its EventHandler                
            }
            // else { } //if not, ignore
        }

        /// <summary>
        /// Changes the original texture.
        /// Note:Texture has to be created with HLSLProcessor's graphics device.
        /// </summary>
        /// <param name="newTexture">The new texture.</param>
        public void ChangeTexture(Texture2D newTexture)
        {
            if (texture == null)
                throw new ArgumentException
                   ("Texture can be changed primaly after starting " +
                    "HLSLProcessor. Use HLSLProcessor.Begin(..)-method first.");
            texture = newTexture;
        }

        /// <summary>
        /// Resets the texture to original texture from 
        /// starting ( HLSLProcessor.Begin(...) ).
        /// </summary>
        public void ResetTexture()
        {
            texture = original;
            // store a copy of original texture
            original = RenderToTexture();
        }

        /// <summary>
        /// Starts the HLSL image processor.
        /// </summary>
        /// <param name="bitmap">Source image to process.</param>
        /// <remarks>Inits all necessary devices and
        /// informations for shader based image processing.</remarks>
        public void Begin(Bitmap bitmap)
        {
            renderControl = new Control();
            renderControl.Width = bitmap.Width;
            renderControl.Height = bitmap.Height;
            Begin(bitmap, renderControl);
        }

        /// <summary>
        /// Starts the image processor by processing an image.
        /// </summary>
        /// <param name="bitmap">Source image to process.</param>
        /// <param name="control">Windows control using for online rendering 
        /// process.</param>
        /// <remarks>Inits all necessary devices and
        /// informations for shader based image processing.</remarks>
        public void Begin(Bitmap bitmap, Control control)
        {
            CheckBegin();
            renderControl = control;
            InitGraphics();
            ImageConverter.BitmapToTexture(bitmap, graphics, out texture);            
            // store a copy of original texture
            ImageConverter.BitmapToTexture(bitmap, graphics, out original);            
        }

        /// <summary>
        /// Starts the image processor by processing a video.
        /// </summary>
        /// <param name="video">The video filename.</param>
        /// <param name="control">Windows control using for online rendering 
        /// process.</param>
        public void Begin(string video, Control control)
        {
            CheckBegin();
            IsVideo = true;
            renderControl = control;
            VideoPlayer = new VideoPlayer(video);
            //videoPlayer.OnVideoComplete += new EventHandler(videoPlayer_OnVideoComplete);

            //// Slow the game speed to match the video speed.
            //// Not required, but can give more CPU time to DirectShow.
            //// Helps when displaying very high resolution videos.
            //this.IsFixedTimeStep = true;
            //if (videoPlayer.MillisecondsPerFrame != -1)
            //    this.TargetElapsedTime = TimeSpan.FromMilliseconds(videoPlayer.MillisecondsPerFrame);

            InitGraphics();
            VideoPlayer.outputFrame = new Texture2D(
                graphics, 
                VideoPlayer.VideoWidth, 
                VideoPlayer.VideoHeight, 
                1, 
                TextureUsage.None, 
                SurfaceFormat.Color);
            texture = VideoPlayer.OutputFrame;
            VideoPlayer.CurrentState = VideoState.Playing;
        }

        /// <summary>
        /// Stops render process. Disposes the used windows control and 
        /// the graphics device.
        /// </summary>
        public void End()
        {
            HasBegun = false;
            IsVideo = false;
            if (VideoPlayer != null)
            {
                VideoPlayer.Dispose();
                VideoPlayer = null;
            }
            ////renderControl.Dispose();

            // Disposing makes errors by closing forms with playing videos
            //graphics.Dispose();
            //texture.Dispose();
            //original.Dispose();
            //Filter.Dispose();            
        }

        /// <summary>
        /// Renders online the image processing results into a windows control.
        /// </summary>
        public void Render()
        {
            if (HasBegun)
            {
                try
                {
                    // Update the Video Player
                    if (IsVideo)
                    {
                        VideoPlayer.Update();
                        filter.Texture = texture; // Weg???
                    }
                    graphics.Clear(Color.AliceBlue);
                    //Console.WriteLine("Time: " + videoPlayer.CurrentPositionAsTimeString 
                    //+ " / " + videoPlayer.DurationAsTimeString, new Vector2(2, 2), Color.White);

                    filter.RenderEffect();
                    graphics.Present();
                }
                catch (DeviceLostException)
                {
                    FireDeviceLostEvent();
                }
            }
            else
                throw new Exception(
                    "Processor has to be started first. " + 
                    "Please use Processor.Begin().");
        }

        /// <summary>
        /// Renders offline the image processing routines and stores the
        /// result image into a texture.
        /// </summary>
        /// <returns>Result texture.</returns>
        public Texture2D RenderToTexture()
        {            
            return filter.RenderEffectToTexture();            
        }

        /// <summary>
        /// Renders offline the image processing routines and stores the
        /// result image into a new image.
        /// </summary>
        /// <returns>Result image.</returns>
        public Bitmap RenderToBitmap()
        {
            Texture2D tex = filter.RenderEffectToTexture();
            return ImageConverter.TextureToRGB(tex);
        }
    }
}
